Android 中使用 SVG 简介
概述
可缩放矢量图 (Scalable Vector Graph,以下简称 SVG),可以大大减小 Android app 中打包的图像资源大小,不用担心图片模糊的问题,很适合用于简单风格或者几何风格的图像。此外在 Android 中,SVG 极大的灵活性,远非其它 drawable 类型(shape,说的就是你)能比。
事实上不光是 Android,SVG 在 Web 中也应用广泛,维基百科中大量的图表使用了 SVG,来减少网络传输资源的大小。比如维基百科的 Android (operating system)) 中就有一幅 Android Logo 图片 是 SVG。如果我们下载这个文件,会发现它是一个文本文件:
1 | <?xml version="1.0" encoding="UTF-8"?> |
理论上讲,矢量图就是一个文本文件,里面按确定规则描述了这个图片有哪些几何元素,渲染时按照这些规则把元素一一绘制出来就好了,所以不会有模糊的问题。不过元素越复杂,越不像是几何元素,那么这个文本文件就会越大(因为需要非常多的描述),最后绘制也需要画比较多东西。
理论是很简单,不过要在 Android 中使用矢量图,我们就需要了解一下它的那些规则了。
SVG 文本的结构
Android 中使用 SVG,我们会创建 drawable XML 文件,里面使用 vector
标签,如下面示例:
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
上面的 vector drawable 预览:
可见 Android 用的 vector drawable 似乎和 web 用的 svg 标签格式不一样。的确不同,不过观察一下,它们都有 path 标签,正是一个一个 path 组成了矢量图的内容。而 path 中的 path data 就是一段信息量很大的文本,里面既有数字也有字母和标点。所幸这个 path data 的规则是通用的,属于 W3C 制定的一套标准,在各个平台都一样。
Path Data
Path data 中的字母,被称作命令,一个命令后面跟的 0 个或多个用逗号或空格分隔的数(可以是整数也可以是小数),就是这个命令的参数。这有点像是 Unix 命令。
命令的大小写有不同。大写表示命令后面的参数当做坐标时是绝对坐标,小写时表示参数当做相对坐标(相对于该命令之前,path 已经抵达的位置点)。
最常见命令:
- M / m 命令表示移动(Move),参数是两个数,组成一对坐标。这个命令通常用来指定 path 的起点。
- L / l 命令表示画直线(Line),参数也是两个数,组成一对坐标。这个命令是最简单的指定 path 下一个位置点的方式,即显示指定下一个点的位置,从当前位置画直线过去。
- Z / z,画一条直线到起点,结束这个 path,没有参数。
- H / h,画一条水平线(Horizontal),参数是一个数,表示水平线的终点的 x 坐标。类似的命令 V / v 表示画竖直线。
实例1:
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
实例2:
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" |
上面两个实例的效果是一样的,只不过第二个例子用到了小写的 l 命令,所以那个 -300,0 是相对坐标,即相对于 L 命令之后到达位置点的坐标。以上两个例子的预览图:
Animated vector drawable
Android 中还支持 animated-vector 标签来创建动画矢量图。最常见的做法是,把 vector 的 pathData 当做一个属性,在动画中改变这个属性,从而让矢量图动起来。下面是一个简单的例子:
drawable/square.xml
,定义有一个正方形的 vector drawable:
1 | <?xml version="1.0" encoding="utf-8"?> |
animator/square_animator.xml
,它的 propertyName 指定了它要更改 target object 的什么属性,valueType 则指明这个 property 是一种 path:
1 | <?xml version="1.0" encoding="utf-8"?> |
drawable/animated_square.xml
,drawable 属性指定一个 vector drawable 为目标,而 target 标签中的 name 属性用来指定需要更改目标 vector drawable 中的哪个 path:
1 | <?xml version="1.0" encoding="utf-8"?> |
AnimatedDrawable 类实现了 Animatable2 接口,所以调用它的 start() 方法,这个 drawable 就能动起来了:
椭圆弧
有了 L 命令,我们可以画三角形、平行四边形、多边形等等。可以指定我们要画的东西占据 android drawable 的多大空间。接下来记录一下圆弧的简单绘制方式。有了这些,我们就能做许多其它 drawable 类型不能做的事了。
在 web svg 中,圆是很简单的,只需要圆心坐标和半径就可以。但是 Android vector drawable 中更复杂。需要两段圆弧拼成一个圆。不过这个复杂性正好引出了椭圆弧的绘制规则,也就是 A / a 命令(elliptical arc)。
A / a 命令有 7 个参数,这 7 个参数依序命令为 rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y.
- rx 和 ry 表示椭圆的长半轴和短半轴(也就是高中数学里的椭圆方程 中的 和 ).
- x-axis-rotation 表示椭圆的长轴相对于 x 轴的夹角,单位是弧度单位的 °,负数表示逆时针旋转长半轴,正数表示顺时针旋转。
中途解释:
rx 和 rx 确定了椭圆的大小和形状,但没确定位置。当椭圆弧的起点 (path 的当前位置点)和终点(最后两个参数)固定后,大小和形状固定的椭圆需要经过这两个点,但椭圆本身依然可以在这两个点上滑动和翻转(只要翻转后长轴方向没变),所以才需要 x-axis-rotation 参数确定一个角度,从而椭圆不能滑动。
然而,在角度固定时,下图中 1, 2, 3, 4 所指的 4 条弧表明,还是有 4 种情况,这时候就需要参数 large-arc-flag 和 sweep-flag 来四选一了。
- large-arc-flag 这个参数为 0 时表示这个弧度是小角度弧度,为 1 表示大角度弧度。上图中 1 和 3 所指的两条弧为小角度,而 2 和 4 所指为大角度。
- sweep-flag 这个参数,为 0 表示逆时针画弧,1 表示顺时针画弧。上图中 1 和 2 是顺时针,而 3 和 4 则是逆时针画弧。
- x, y 这两个参数是椭圆弧终点的坐标。
有了椭圆弧,就可以拼接两段椭圆弧形成椭圆,而圆只是特殊的椭圆。下面是一个椭圆的例子。
1 | <?xml version="1.0" encoding="utf-8"?> |
上面实例中,两段椭圆弧,都是从 (0, 200) 到 (400, 200),唯一不同点是,其中一段顺时针,另一段逆时针,从而正好构成一个椭圆:
实践
给公司 app 的桌面图标做的 vector drawable,其中的 clip path 使得只有这个区域范围内的东西才会显示出来,从而达成一个远交矩阵的效果:
1 | <?xml version="1.0" encoding="utf-8"?> |
上面的 vector drawable 的效果就是这个 app 的图标了(当然,是带圆角的)。
其它
除了使用 fill color 来填充 path 内部,还可以用 stroke color 和其它属性来控制笔画的颜色和样式。矢量图规则是非常庞大和复杂的。可以参考 Android 开发文档,或者是 W3C 的文档。
矢量图在 Android 中的应用已经相当广泛了,只需扫一眼 Google 系列的各个 app,就会发现它们的图标都是扁平化的几何风格,如果解压 apk 再去看 app 桌面图标资源文件,就能确定他们的确用的是矢量图。乔布斯之后,扁平化风格大行其道,这可能也是矢量图的一阵春风吧。